-- 这代码都要堆成屎山了,写了两天给人写崩溃了
require "prefabutil" -- 服务 MakePlacer 函数.

local sw_moonlight_glass_tree_defs = require("prefabs/sw_moonlight_glass_tree_defs")
local TREE_DEFS                    = sw_moonlight_glass_tree_defs.TREE_DEFS
local PLANT_DATA                   = sw_moonlight_glass_tree_defs.PLANT_DATA

----------------------------------------------------------------------------------------------------------------------

local AMBIENCE_SOUNDNAME           = "amb"

local total_day_time               = 30 * 16

local GROW_TIME                    = { base = 8 * total_day_time, random = 1 * total_day_time } -- 7-9 days.

----------------------------------------------------------------------------------------------------------------------

local statedata                    =
{
    {
        name     = "seed",
        idleanim = "idle_seed",
        tag      = "seedstage",
    },
    {
        name     = "short",
        idleanim = "idle_short",
        growanim = "grow_seed_to_short",
    },
    {
        name = "normal",
        idleanim = "idle_normal",
        growanim = "grow_short_to_normal",
    },
    {
        name = "tall",
        idleanim = "idle_tall",
        growanim = "grow_normal_to_tall",
    },
    {
        name = "pillar",
        idleanim = "idle_pillar",
        growanim = "grow_tall_to_pillar",
    },
}

local function OnSetStage(inst)
    if inst.statedata ~= nil and inst.statedata.tag ~= nil then
        inst:RemoveTag(inst.statedata.tag)
    end

    inst.statedata = statedata[inst.components.growable.stage]

    if inst.statedata ~= nil and inst.statedata.tag ~= nil then
        inst:AddTag(inst.statedata.tag)
    end

    inst.AnimState:SetBank(inst._bank)
    inst.AnimState:SetBuild(inst._build)
    inst.AnimState:PlayAnimation(inst.statedata.idleanim, true)

    inst:DoTaskInTime(0, inst.CheckGrowConstraints) -- NOTES(DiogoW): Growable calls StartGrowing after setting a stage.
end

local function Set_Normal(inst)
    inst.statedata = statedata[inst.components.growable.stage]

    inst.AnimState:SetBank(inst._bank)
    inst.AnimState:SetBuild(inst._build)
    inst.AnimState:PlayAnimation(inst.statedata.idleanim, true)

    inst.components.workable:SetWorkAction(ACTIONS.MINE)
    inst.components.workable:SetWorkLeft(13)
    inst.components.workable:SetOnFinishCallback(inst.OnWorkedFinish)
    inst.components.lootdropper:SetChanceLootTable("sw_moonlight_glass_tree_normal")

    inst:AddTag("shelter")
    inst.MiniMapEntity:SetIcon("sw_moonlight_glass_tree.tex")

    inst:DoTaskInTime(0, inst.CheckGrowConstraints)
end

local function Set_Tall(inst)
    inst.statedata = statedata[inst.components.growable.stage]

    inst.AnimState:SetBank(inst._bank)
    inst.AnimState:SetBuild(inst._build)
    inst.AnimState:PlayAnimation(inst.statedata.idleanim, true)

    inst.components.workable:SetWorkAction(ACTIONS.MINE)
    inst.components.workable:SetWorkLeft(16)
    inst.components.workable:SetOnFinishCallback(inst.OnWorkedFinish)
    inst.components.lootdropper:SetChanceLootTable("sw_moonlight_glass_tree_tall")

    inst:AddTag("shelter")
    inst.MiniMapEntity:SetIcon("sw_moonlight_glass_tree.tex")

    inst:DoTaskInTime(0, inst.CheckGrowConstraints)
end

local function OnGrowth(inst)
    if inst:IsAsleep() then
        return
    end

    inst.SoundEmitter:PlaySound(inst.sounds.grow)

    inst.AnimState:PlayAnimation(inst.statedata.growanim)
    inst.AnimState:PushAnimation(inst.statedata.idleanim, true)
end

local function OnGrowthFull(inst)
    local tree = SpawnPrefab(inst.type)

    if tree.components.pickable ~= nil then
        tree.components.pickable:MakeEmpty()
    end

    if tree ~= nil then
        tree.Transform:SetPosition(inst.Transform:GetWorldPosition())

        if inst:IsAsleep() then
            tree.AnimState:PlayAnimation("idle_pillar", true)
        else
            tree.SoundEmitter:PlaySound(inst.sounds.grow)

            tree.AnimState:PlayAnimation("grow_tall_to_pillar")
            tree.AnimState:PushAnimation("idle_pillar", true)
        end
    end

    inst:Remove()

    return tree -- Mods.
end

-- 获取生长时间
local function GetGrowthTime(inst, state)
    return GetRandomWithVariance(GROW_TIME.base, GROW_TIME.random)
end

local GROWTH_STAGES =
{
    {
        time = GetGrowthTime,
        fn = OnSetStage,
    },
    {
        time = GetGrowthTime,
        growfn = OnGrowth,
        fn = OnSetStage,
    },
    {
        time = GetGrowthTime,
        growfn = OnGrowth,
        fn = Set_Normal,
    },
    {
        time = GetGrowthTime,
        growfn = OnGrowth,
        fn = Set_Tall,
    },
    {
        fn = OnGrowthFull,
    },
}

----------------------------------------------------------------------------------------------------------------------

local function Sapling_DigUp(inst)
    local loot = nil

    if inst.components.lootdropper ~= nil then
        if inst.components.growable.stage <= 1 then
            loot = inst.components.lootdropper:SpawnLootPrefab("sw_moonlight_seed")
        else
            loot = inst.components.lootdropper:SpawnLootPrefab("sw_moonlight_glass_tree_sapling_item")
        end
    end

    inst:Remove()

    return loot -- Mods.
end

local function ShakeCamera(inst)
    ShakeAllCameras(CAMERASHAKE.FULL, .25, .03, .5, inst, 6)
end

local function Sapling_OnWorkedFinish(inst, worker)
    inst.SoundEmitter:PlaySound(inst.sounds.onworkfinish)

    if inst.components.growable.stage == 3 then
        inst.AnimState:PlayAnimation("fall_normal")
        inst.AnimState:PushAnimation("stump_normal")
    elseif inst.components.growable.stage == 4 then
        inst.AnimState:PlayAnimation("fall_tall")
        inst.AnimState:PushAnimation("stump_tall")
    else
        inst.AnimState:PlayAnimation("fall_normal")
        inst.AnimState:PushAnimation("stump_normal")
    end

    inst.components.lootdropper:DropLoot()

    inst:MakeStump()

    inst:DoTaskInTime(16 * FRAMES, ShakeCamera)
end

local function Sapling_OnDeploy(inst, pt)
    local sapling = SpawnPrefab(inst.type .. "_sapling")
    sapling.SoundEmitter:PlaySound("dontstarve/wilson/plant_tree")
    sapling.Transform:SetPosition(pt:Get())

    sapling.components.growable:SetStage(2)
    sapling.components.growable:StartGrowing()

    sapling:CheckGrowConstraints()

    if inst.components.stackable ~= nil then
        inst.components.stackable:Get():Remove()
    else
        inst:Remove()
    end

    return sapling -- Mods.
end

local function Seed_OnDeploy(inst, pt)
    local sapling = SpawnPrefab(inst.type .. "_sapling")
    sapling.SoundEmitter:PlaySound("dontstarve/wilson/plant_tree")
    sapling.Transform:SetPosition(pt:Get())

    sapling:CheckGrowConstraints()

    if inst.components.stackable ~= nil then
        inst.components.stackable:Get():Remove()
    else
        inst:Remove()
    end

    return sapling -- Mods.
end

local function CheckGrowConstraints(inst)
    local isnewmoon = TheWorld.state.isnewmoon

    if isnewmoon then
        if inst.components.growable then
            inst.components.growable:Pause("isnewmoon")
        end
        if inst.components.pickable then
            inst.components.pickable:Pause()
        end
    else
        if inst.components.growable then
            inst.components.growable:Resume("isnewmoon")
        end
        if inst.components.pickable then
            inst.components.pickable:Resume()
        end
    end
end

local function DoMagicGrowthFn(inst, doer)
    inst.magic_growth_delay = nil

    inst:CheckGrowConstraints()

    if not inst.components.growable:IsGrowing() then
        return -- Paused or stopped.
    end

    inst.components.growable:DoGrowth()
end

----------------------------------------------------------------------------------------------------------------------

local function DigUpStump(inst, digger)
    inst.components.lootdropper:SetChanceLootTable("sw_moonlight_glass_tree_stump")
    inst.components.lootdropper:DropLoot()

    inst:Remove()
end

local function LaunchItem(inst, target, item)
    if item.Physics ~= nil and item.Physics:IsActive() then
        local x, y, z = item.Transform:GetWorldPosition()
        item.Physics:Teleport(x, .1, z)

        x, y, z = inst.Transform:GetWorldPosition()
        local x1, y1, z1 = target.Transform:GetWorldPosition()
        local angle = math.atan2(z1 - z, x1 - x) + (math.random() * 20 - 10) * DEGREES
        local speed = 5 + math.random() * 2
        item.Physics:SetVel(math.cos(angle) * speed, 10, math.sin(angle) * speed)
    end
end

local function OnWorked(inst, worker, workleft, numworks)
    local tool = worker and worker.components.inventory and worker.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS)
    if tool then
        if tool.prefab ~= "multitool_axe_pickaxe" and tool.prefab ~= "shovel_lunarplant" and tool.prefab ~= "pickaxe_lunarplant" then
            if workleft > 0 then
                inst.components.workable.workleft = workleft + numworks
            else
                inst.components.workable.workleft = numworks
            end
            worker.components.inventory:DropItem(tool)
            LaunchItem(inst, worker, tool)
            if worker.components.talker then
                worker:DoTaskInTime(20 * FRAMES, function()
                    worker.components.talker:Say(STRINGS.SW_ACTIONS.SW_WORK_FAIL)
                end)
            end
        end
    end
end

local function MakeStump(inst)
    local work_action = ACTIONS.DIG
    local work_left = 1

    inst:RemoveComponent("workable")
    if inst.components.pickable then
        inst:RemoveComponent("pickable")
    end
    if inst.components.growable ~= nil then
        inst.components.growable:StopGrowing()
    end

    inst:AddTag("stump")
    inst:RemoveTag("shelter")

    RemovePhysicsColliders(inst)

    if inst.DynamicShadow ~= nil then
        inst.DynamicShadow:Enable(false)
    end

    inst.SoundEmitter:KillSound(AMBIENCE_SOUNDNAME)

    inst.MiniMapEntity:SetIcon("sw_moonlight_glass_tree_stump.tex")
    inst.MiniMapEntity:SetPriority(1)

    inst:AddComponent("workable")
    inst.components.workable:SetWorkAction(work_action)
    inst.components.workable:SetOnWorkCallback(OnWorked)
    inst.components.workable:SetOnFinishCallback(DigUpStump)
    inst.components.workable:SetWorkLeft(work_left)
end

local function Full_OnWorkedFinish(inst, worker)
    inst.SoundEmitter:PlaySound(inst.sounds.onworkfinish)

    inst.AnimState:PlayAnimation("fall_pillar")
    inst.AnimState:PushAnimation("stump_pillar")

    inst.components.lootdropper:DropLoot()

    if inst.components.pickable and inst.components.pickable:CanBePicked() then
        for i = 1, inst.components.pickable.numtoharvest do
            inst.components.lootdropper:SpawnLootPrefab(inst.components.pickable.product)
        end
    end

    inst:MakeStump()

    inst:DoTaskInTime(16 * FRAMES, ShakeCamera)
end

local function Full_OnPickedFn(inst, picker, loot)
    inst.AnimState:PlayAnimation("idle_pillar", true)
    inst.components.pickable:SpawnProductLoot(picker)
    inst.components.pickable:SpawnProductLoot(picker)
end

local function Full_OnMakeEmptyFn(inst)
    inst.AnimState:PlayAnimation("idle_pillar", true)
end

local function Full_OnRegenFn(inst)
    -- inst.AnimState:PlayAnimation("idle_fruit", true)
    if inst:IsAsleep() then
        inst.AnimState:PlayAnimation("idle_fruit", true)
    else
        inst.AnimState:PlayAnimation("grow_fruit")
        inst.AnimState:PushAnimation("idle_fruit", true)

        inst.SoundEmitter:PlaySound(inst.sounds.onshowfruits)
    end
end

local function Full_OnSave(inst, data)
    if inst:HasTag("stump") then
        data.stump = true
    end
    if inst.components.pickable ~= nil then
        data.CanBePicked = inst.components.pickable:CanBePicked()
    end
end

local function Full_OnLoad(inst, data)
    if data ~= nil and data.CanBePicked ~= nil and data.CanBePicked == true then
        inst.AnimState:PlayAnimation("idle_fruit", true)
    else
        inst.AnimState:PlayAnimation("idle_pillar", true)
    end
    if data ~= nil and data.stump then
        inst:MakeStump()
        inst.AnimState:PlayAnimation("stump_pillar")
    end
end

local function Full_OnEntityWake(inst)
    if not inst:HasTag("stump") then
        inst.SoundEmitter:PlaySound(inst.sounds.ambience, AMBIENCE_SOUNDNAME)
    end
end

local function Full_OnEntitySleep(inst)
    inst.SoundEmitter:KillSound(AMBIENCE_SOUNDNAME)
end

local function GetStatus(inst, data)
    return inst:HasTag("stump") and "STUMP" or nil
end

local function Sapling_OnSave(inst, data)
    if inst:HasTag("stump") then
        data.stump = true
        data.stage = inst.components.growable.stage
    end
end

local function Sapling_OnLoad(inst, data)
    if data ~= nil and data.stump and data.stage then
        inst:MakeStump()
        if data.stage == 3 then
            inst.AnimState:PlayAnimation("stump_normal")
        elseif data.stage == 4 then
            inst.AnimState:PlayAnimation("stump_tall")
        end
    end
end

----------------------------------------------------------------------------------------------------------------------

local function MakeAncientTree(name, data)
    local assets = {
        Asset("ANIM", "anim/" .. data.build .. ".zip"),
        Asset("ANIM", "anim/sw_moonlight_seed.zip"),
        Asset("ANIM", "anim/sw_sapling.zip"),
        Asset("MINIMAP_IMAGE", name .. "_stump.tex"),

        Asset("SCRIPT", "scripts/prefabs/sw_moonlight_glass_tree_defs.lua"),
    }

    local prefabs_tree = {
        data.fruit_prefab,
        name .. "_sapling",
        name .. "_sapling_item",
        name .. "_sapling_item_placer",
        "sw_moonlight_seed",
        "sw_moonlight_seed_placer"
    }

    SetSharedLootTable(name .. "_normal", data.LOOT.normal)
    SetSharedLootTable(name .. "_tall", data.LOOT.tall)
    SetSharedLootTable(name .. "_pillar", data.LOOT.pillar)
    SetSharedLootTable(name .. "_stump", data.LOOT.stump)

    local function FullTreeFn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddSoundEmitter()
        inst.entity:AddMiniMapEntity()
        inst.entity:AddNetwork()

        if data.shadow_size ~= nil then
            inst.entity:AddDynamicShadow()
            inst.DynamicShadow:SetSize(data.shadow_size, data.shadow_size)
        end

        -- 物理碰撞半径和限制种植建造区域
        MakeObstaclePhysics(inst, data.physics_rad)
        inst:SetDeploySmartRadius(DEPLOYSPACING_RADIUS[DEPLOYSPACING.PLACER_DEFAULT] / 2) -- seed deployspacing/2

        inst.MiniMapEntity:SetIcon(name .. ".tex")
        inst.MiniMapEntity:SetPriority(3)

        inst.AnimState:SetBank(data.build)
        inst.AnimState:SetBuild(data.bank)
        inst.AnimState:PlayAnimation("idle_fruit", true)

        inst:AddTag("plant")
        inst:AddTag("tree")
        inst:AddTag("no_force_grow") -- 禁用树肥
        inst:AddTag("ancienttree")   -- 禁止造林学催熟,不过旧版应用园艺学还是可以催熟
        inst:AddTag("event_trigger") -- 全局搜索,猜测是,不被影人,雇佣的生物等视作工作目标

        if data.shelter then
            inst:AddTag("shelter")
        end

        if data.snowcovered then
            MakeSnowCoveredPristine(inst)
        end

        if data.common_postinit ~= nil then
            data.common_postinit(inst)
        end

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst.type             = name
        inst.sounds           = TREE_DEFS[inst.type].sounds
        inst.directional_fall = data.directional_fall
        inst._bank            = data.bank
        inst._build           = data.build

        local multcolor       = 0.75 + math.random() * 0.25
        inst.AnimState:SetMultColour(multcolor, multcolor, multcolor, 1)

        inst.MakeStump = MakeStump

        inst:AddComponent("inspectable")
        inst.components.inspectable.getstatus = GetStatus

        inst:AddComponent("lootdropper")
        inst.components.lootdropper:SetChanceLootTable("sw_moonlight_glass_tree_pillar")

        inst:AddComponent("workable")
        inst.components.workable:SetWorkAction(ACTIONS[data.workaction])
        inst.components.workable:SetOnWorkCallback(OnWorked)
        inst.components.workable:SetOnFinishCallback(Full_OnWorkedFinish)
        inst.components.workable:SetWorkLeft(18)

        inst:AddComponent("pickable")
        inst.components.pickable.picksound = data.sounds.onpicked

        inst.components.pickable:SetUp(data.fruit_prefab, PLANT_DATA.fruit_regen.max, data.numtoharvest)
        inst.components.pickable.onregenfn   = Full_OnRegenFn
        inst.components.pickable.onpickedfn  = Full_OnPickedFn
        inst.components.pickable.makeemptyfn = Full_OnMakeEmptyFn

        inst:WatchWorldState("isnewmoon", CheckGrowConstraints)

        MakeHauntableWork(inst)

        if data.snowcovered then
            MakeSnowCovered(inst)
        end

        inst.OnSave        = Full_OnSave
        inst.OnLoad        = Full_OnLoad

        inst.OnEntityWake  = Full_OnEntityWake
        inst.OnEntitySleep = Full_OnEntitySleep

        if data.master_postinit ~= nil then
            data.master_postinit(inst)
        end

        return inst
    end

    local function SaplingFn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddSoundEmitter()
        inst.entity:AddMiniMapEntity()
        inst.entity:AddNetwork()

        inst:SetDeploySmartRadius(DEPLOYSPACING_RADIUS[DEPLOYSPACING.PLACER_DEFAULT] / 2) -- seed deployspacing/2

        inst.AnimState:SetBank("sw_moonlight_glass_tree")
        inst.AnimState:SetBuild("sw_moonlight_glass_tree")
        inst.AnimState:PlayAnimation("idle_seed", true)

        if TUNING.SHADOWWOVEN.TREE_GROWTH == "Yes" then
            inst:AddTag("silviculture") -- 可以被应用造林学催熟
        end
        inst:AddTag("event_trigger")

        if data.common_postinit ~= nil then
            data.common_postinit(inst)
        end

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst.type                 = name
        inst.sounds               = TREE_DEFS[inst.type].sounds
        inst._bank                = data.bank
        inst._build               = data.build

        inst.CheckGrowConstraints = CheckGrowConstraints
        inst.OnWorkedFinish       = Sapling_OnWorkedFinish
        inst.MakeStump            = MakeStump

        local multcolor           = 0.75 + math.random() * 0.25
        inst.AnimState:SetMultColour(multcolor, multcolor, multcolor, 1)

        inst:AddComponent("lootdropper")

        inst:AddComponent("inspectable")
        inst.components.inspectable.getstatus = GetStatus

        inst:AddComponent("workable")
        inst.components.workable:SetWorkAction(ACTIONS.DIG)
        inst.components.workable:SetOnWorkCallback(OnWorked)
        inst.components.workable:SetOnFinishCallback(Sapling_DigUp)
        inst.components.workable:SetWorkLeft(1)

        inst:AddComponent("growable")
        inst.components.growable.magicgrowable = true
        inst.components.growable.domagicgrowthfn = DoMagicGrowthFn
        inst.components.growable.stages = GROWTH_STAGES
        inst.components.growable:SetStage(1)
        inst.components.growable:StartGrowing()

        inst:WatchWorldState("isnewmoon", inst.CheckGrowConstraints)

        inst:DoTaskInTime(0, inst.CheckGrowConstraints)

        MakeHauntableWork(inst)

        inst.OnSave = Sapling_OnSave
        inst.OnLoad = Sapling_OnLoad

        if data.master_postinit ~= nil then
            data.master_postinit(inst)
        end

        return inst
    end

    local function SaplingItemFn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddSoundEmitter()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank("sw_sapling")
        inst.AnimState:SetBuild("sw_sapling")
        inst.AnimState:PlayAnimation("moonlight_glass")

        inst:AddTag("deployedplant")

        MakeInventoryFloatable(inst, "large", 0.2, 0.55)

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst.type = name
        inst.sounds = TREE_DEFS[inst.type].sounds

        inst:AddComponent("inventoryitem")

        inst:AddComponent("inspectable")

        inst:AddComponent("tradable")

        inst:AddComponent("deployable")
        inst.components.deployable:SetDeploySpacing(DEPLOYSPACING.PLACER_DEFAULT)
        inst.components.deployable.ondeploy = Sapling_OnDeploy

        MakeHauntableLaunch(inst)

        return inst
    end

    local function SeedFn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddSoundEmitter()
        inst.entity:AddNetwork()

        MakeInventoryPhysics(inst)

        inst.AnimState:SetBank("sw_moonlight_seed")
        inst.AnimState:SetBuild("sw_moonlight_seed")
        inst.AnimState:PlayAnimation("idle")

        inst:AddTag("deployedplant")

        MakeInventoryFloatable(inst, "large", 0.2, 0.55)

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        inst.type = name
        inst.sounds = TREE_DEFS[inst.type].sounds

        inst:AddComponent("inventoryitem")

        inst:AddComponent("inspectable")

        inst:AddComponent("deployable")
        inst.components.deployable:SetDeploySpacing(DEPLOYSPACING.PLACER_DEFAULT)
        inst.components.deployable.ondeploy = Seed_OnDeploy

        MakeHauntableLaunch(inst)

        return inst
    end

    return
        Prefab(name, FullTreeFn, assets, prefabs_tree),
        Prefab(name .. "_sapling", SaplingFn, assets),
        Prefab(name .. "_sapling_item", SaplingItemFn, assets),
        MakePlacer(name .. "_sapling_item_placer", data.bank, data.build, "idle_short"),
        Prefab("sw_moonlight_seed", SeedFn, assets),
        MakePlacer("sw_moonlight_seed_placer", data.bank, data.build, "idle_seed")
end

local tree_prefabs = {}

for name, data in pairs(TREE_DEFS) do
    ConcatArrays(tree_prefabs, { MakeAncientTree(name, data) })
end

return unpack(tree_prefabs)
